package src.glmodel;

import org.lwjgl.opengl.GL11;
import src.Message;

/**
 * This class loads and renders a mesh from an OBJ file format. The mesh can
 * have multiple materials, including texture images. Uses GL_Mesh to load a
 * .obj file and GLMaterialLIb to load the .mtl file. It assumes the .obj .mtl
 * and any texture images are present in the same folder. Also has a function,
 * renderTextured() to draw a mesh with no groups or materials. The entire mesh
 * will be drawn as one group of triangles with one texture.
 */
public class GLModel
{
	// a default material to use if none is specified
	public static GLMaterial	defaultMtl	= new GLMaterial();

	public GL_Mesh				mesh;							// holds vertex, triangle and group information
	public int					displayListID;

	// !!!! this will be null. NEED to check this in OBJ_Reader and check renderGroups() (BROKEN!!!)
	public GLMaterial[]			groupMaterials;				// holds material for each face group (1 group to 1 material)

	public GLModel(String filename)
	{
		// load OBJ file
		mesh = loadMesh(filename);
	}

	/**
	 * read the given .obj file into a GL_Mesh object
	 * 
	 * @param filename
	 *            (must end in .obj)
	 * @return the loaded GL_Mesh
	 */
	public GL_Mesh loadMesh(String filename)
	{
		if (filename.toUpperCase().endsWith(".OBJ"))
		{
			GL_OBJ_Importer importer = new GL_OBJ_Importer();
			mesh = importer.load(filename);
		}
		else
		{
			GL_3DS_Importer importer = new GL_3DS_Importer();
			mesh = importer.load(filename);
			Message.msg("GLMeshRenderer.loadMesh(): WARNING 3DS files functionality is limited");
		}
		return mesh;
	}

	/**
	 * Render mesh into a displaylist and store the listID in the flowercenter
	 * object.
	 * 
	 * @param PR
	 *            PetalRing to draw
	 */
	public void makeDisplayList()
	{
		// call the displaylist
		if (displayListID == 0)
		{
			displayListID = GL11.glGenLists(1); // allocate a display list
			GL11.glNewList(displayListID, GL11.GL_COMPILE); // Start the list
			render(mesh); // render the mesh
			GL11.glEndList(); // done
		}
	}

	/**
	 * return the display list ID created by makeDisplayList()
	 */
	public int getDisplayListID()
	{
		return displayListID;
	}

	/**
	 * recalculate normals on the mesh object
	 * 
	 * @see GL_Mesh.regenerateNormals()
	 * @see GL_Mesh.setSmoothingAngle()
	 */
	public void regenerateNormals()
	{
		if (mesh != null)
		{
			mesh.regenerateNormals();
		}
	}

	/**
	 * Draw the model. Calls the displaylist if one is created, or calls
	 * renderGroups()
	 */
	public void render()
	{
		if (displayListID == 0)
		{
			render(mesh);
		}
		else
		{
			GL11.glCallList(displayListID);
		}
	}

	/**
	 * Draw one group from the mesh. This will activate the correct material for
	 * the group (including textures).
	 * 
	 * @param groupName
	 *            name of group (from obj file)
	 */
	public void renderGroup(String groupName)
	{
		int GID = -1; // group id

		// find group by name
		for (int g = 0; g < mesh.numGroups(); g++)
		{
			if (mesh.getGroupName(g).equals(groupName))
			{
				GID = g;
				break;
			}
		}
		if (GID == -1)
		{
			return;
		}

		// draw the triangles in this group
		GLMaterial[] materials = mesh.materials; // loaded from the .mtl file
		GL_Triangle[] triangles = mesh.getGroupFaces(GID); // each group may have a material
		GLMaterial mtl;
		GL_Triangle t;
		int currMtl = -1;
		int i = 0;

		// draw all triangles in object
		for (i = 0; i < triangles.length;)
		{
			t = triangles[i];

			// activate new material and texture
			currMtl = t.materialID;
			mtl = (materials != null && materials.length > 0 && currMtl >= 0) ? materials[currMtl] : defaultMtl;
			mtl.apply();
			GL11.glBindTexture(GL11.GL_TEXTURE_2D, mtl.textureHandle);

			// draw triangles until material changes
			GL11.glBegin(GL11.GL_TRIANGLES);
			for (; i < triangles.length && (t = triangles[i]) != null && currMtl == t.materialID; i++)
			{
				GL11.glTexCoord2f(t.uvw1.x, t.uvw1.y);
				GL11.glNormal3f(t.norm1.x, t.norm1.y, t.norm1.z);
				GL11.glVertex3f((float) t.p1.pos.x, (float) t.p1.pos.y, (float) t.p1.pos.z);

				GL11.glTexCoord2f(t.uvw2.x, t.uvw2.y);
				GL11.glNormal3f(t.norm2.x, t.norm2.y, t.norm2.z);
				GL11.glVertex3f((float) t.p2.pos.x, (float) t.p2.pos.y, (float) t.p2.pos.z);

				GL11.glTexCoord2f(t.uvw3.x, t.uvw3.y);
				GL11.glNormal3f(t.norm3.x, t.norm3.y, t.norm3.z);
				GL11.glVertex3f((float) t.p3.pos.x, (float) t.p3.pos.y, (float) t.p3.pos.z);
			}
			GL11.glEnd();
		}
	}

	/**
	 * This is a simple way to render a mesh with no materials. Draws the mesh
	 * with normals and texture coordinates. Loops through all triangles in the
	 * mesh object (ignores groups and materials).
	 * 
	 * @param o
	 *            mesh object to render
	 */
	public void renderTextured(int textureHandle)
	{
		GL_Triangle t;
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureHandle);
		GL11.glBegin(GL11.GL_TRIANGLES);
		for (int j = 0; j < mesh.triangles.length; j++)
		{ // draw all triangles in object
			t = mesh.triangles[j];

			GL11.glTexCoord2f(t.uvw1.x, t.uvw1.y);
			GL11.glNormal3f(t.norm1.x, t.norm1.y, t.norm1.z);
			GL11.glVertex3f((float) t.p1.pos.x, (float) t.p1.pos.y, (float) t.p1.pos.z);

			GL11.glTexCoord2f(t.uvw2.x, t.uvw2.y);
			GL11.glNormal3f(t.norm2.x, t.norm2.y, t.norm2.z);
			GL11.glVertex3f((float) t.p2.pos.x, (float) t.p2.pos.y, (float) t.p2.pos.z);

			GL11.glTexCoord2f(t.uvw3.x, t.uvw3.y);
			GL11.glNormal3f(t.norm3.x, t.norm3.y, t.norm3.z);
			GL11.glVertex3f((float) t.p3.pos.x, (float) t.p3.pos.y, (float) t.p3.pos.z);
		}
		GL11.glEnd();
	}

	/**
	 * Render a mesh with materials. If no materials exist (none are defined in
	 * the mesh, or the materials file was not found), then a default material
	 * will be applied and texture 0 will be activated (see GLMaterial.java for
	 * the default material settings).
	 */
	public void render(GL_Mesh m)
	{
		GLMaterial[] materials = m.materials; // loaded from the .mtl file
		GLMaterial mtl;
		GL_Triangle t;
		int currMtl = -1;
		int i = 0;

		// draw all triangles in object
		for (i = 0; i < m.triangles.length;)
		{
			t = m.triangles[i];

			// activate new material and texture
			currMtl = t.materialID;
			mtl = (materials != null && materials.length > 0 && currMtl >= 0) ? materials[currMtl] : defaultMtl;
			mtl.apply();
			GL11.glBindTexture(GL11.GL_TEXTURE_2D, mtl.textureHandle);

			// draw triangles until material changes
			GL11.glBegin(GL11.GL_TRIANGLES);
			for (; i < m.triangles.length && (t = m.triangles[i]) != null && currMtl == t.materialID; i++)
			{
				GL11.glTexCoord2f(t.uvw1.x, t.uvw1.y);
				GL11.glNormal3f(t.norm1.x, t.norm1.y, t.norm1.z);
				GL11.glVertex3f((float) t.p1.pos.x, (float) t.p1.pos.y, (float) t.p1.pos.z);

				GL11.glTexCoord2f(t.uvw2.x, t.uvw2.y);
				GL11.glNormal3f(t.norm2.x, t.norm2.y, t.norm2.z);
				GL11.glVertex3f((float) t.p2.pos.x, (float) t.p2.pos.y, (float) t.p2.pos.z);

				GL11.glTexCoord2f(t.uvw3.x, t.uvw3.y);
				GL11.glNormal3f(t.norm3.x, t.norm3.y, t.norm3.z);
				GL11.glVertex3f((float) t.p3.pos.x, (float) t.p3.pos.y, (float) t.p3.pos.z);
			}
			GL11.glEnd();
		}
	}

	public void renderMeshNormals()
	{
		GL_Triangle t;
		GL11.glDisable(GL11.GL_LIGHTING);
		GL11.glColor3f(0, 1, 0);
		GL11.glBegin(GL11.GL_LINES);
		{
			for (int j = 0; j < mesh.triangles.length; j++)
			{ // draw all triangles in object
				t = mesh.triangles[j];
				t.norm1.normalize();
				t.norm2.normalize();
				t.norm3.normalize();

				GL11.glVertex3f((float) t.p1.pos.x, (float) t.p1.pos.y, (float) t.p1.pos.z);
				GL11.glVertex3f((float) (t.p1.pos.x + t.norm1.x), (float) (t.p1.pos.y + t.norm1.y), (float) (t.p1.pos.z + t.norm1.z));

				GL11.glVertex3f((float) t.p2.pos.x, (float) t.p2.pos.y, (float) t.p2.pos.z);
				GL11.glVertex3f((float) (t.p2.pos.x + t.norm2.x), (float) (t.p2.pos.y + t.norm2.y), (float) (t.p2.pos.z + t.norm2.z));

				GL11.glVertex3f((float) t.p3.pos.x, (float) t.p3.pos.y, (float) t.p3.pos.z);
				GL11.glVertex3f((float) (t.p3.pos.x + t.norm3.x), (float) (t.p3.pos.y + t.norm3.y), (float) (t.p3.pos.z + t.norm3.z));
			}
		}
		GL11.glEnd();
		GL11.glEnable(GL11.GL_LIGHTING);
	}

}
